package de.jmda.mview.proj;

import java.awt.Component;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTree;
import javax.swing.JViewport;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;

import org.apache.log4j.Logger;

import de.jmda.gui.swing.mvc.AbstractController;
import de.jmda.gui.swing.mvc.ControllerContextProvider;
import de.jmda.mproc.ProcessingUtilities;
import de.jmda.mproc.TypeElementDictionary;
import de.jmda.mproc.TypeElementDictionarySingleton;
import de.jmda.mproc.swingui.JPanelVisualProcessor;
import de.jmda.mview.DesktopPane;
import de.jmda.mview.DesktopPane.JAXBModel;
import de.jmda.mview.typeshape.TypeShape;
                                                                                       
/**                                                                                    
 * This class was initially generated by {@link
 * de.jmda.gui.swing.mvc.ControllerSupportEnabledGenerator}.    
 * <p>                                                                                 
 * <code>ControllerSupportEnabledGenerator</code> will not regenerate this
 * class, hence modifications will not get lost due to subsequent generation by
 * <code>ControllerSupportEnabledGenerator</code>.                                         
 */
class DiagramsController extends AbstractController<DiagramsModel>
{
	private final static Logger LOGGER =
			Logger.getLogger(DiagramsController.class);

	private DiagramsControllerContext context;                                   

	private DiagramsModel model;

	private TypeElementDictionary typeElementDictionary =
			TypeElementDictionarySingleton.instance();
	
	private DiagramsTreeListener diagramsTreeListener;

	private TypesTreeListener typesTreeListener;

	/**                                                                                   
	 * Constructor                                                                        
	 */                                                                                   
	DiagramsController(ControllerContextProvider controllerContextProvider)      
	{                                                                                     
		super(controllerContextProvider);                                                   
		context =
				(DiagramsControllerContext)
						controllerContextProvider.getControllerContext();
	}                                                                                     
                                                                                       
	@Override                                                                             
	public void refreshControllerContext()                                                
	{                                                                                     
		context =
				(DiagramsControllerContext)
						controllerContextProvider.getControllerContext();
	}
                                                                                       
	@Override                                                                             
	public void setModel(DiagramsModel model)
	{
		this.model = model;
		updateView();
	}
	
	DiagramsControllerContext getDiagramsControllerContext()
	{
		return context;
	}

	private void updateView()
  {
		updateTreeTypes();
		updateTreeDiagrams();
  }

	private void updateTreeDiagrams()
  {
		// shortcut
		JTree treeDiagrams = context.getTreeDiagrams();
		
		treeDiagrams.removeAll();
		
		// set cell renderer
		treeDiagrams.setCellRenderer(new DiagramsTreeCellRenderer());
		
		// set cell editor
		treeDiagrams.setCellEditor(
				new DiagramsTreeCellEditor(
						treeDiagrams,
						(DefaultTreeCellRenderer) treeDiagrams.getCellRenderer()));
		
		// make tree cells editable
		treeDiagrams.setEditable(true);
		
		// show collapse / expand handles on root nodes
		treeDiagrams.setShowsRootHandles(true);

		// define root node
		DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();

		// populate tree
		for (DiagramGroup diagramGroup : model.getDiagramGroups())
		{
			updateTreeDiagramGroup(rootNode, diagramGroup);
		}

		// build new tree model from root node
		TreeModel treeModel = new DefaultTreeModel(rootNode);
		
		// remove all tree selection listeners
		TreeSelectionListener[] treeSelectionListeners =
				treeDiagrams.getTreeSelectionListeners();
		
		for (TreeSelectionListener treeSelectionListener : treeSelectionListeners)
		{
			treeDiagrams.removeTreeSelectionListener(treeSelectionListener);
		}
		
		// do not remove all mouse listeners from tree, otherwise the mouse will
		// not work for the tree
		
		// create new diagrams tree listener
		diagramsTreeListener = new DiagramsTreeListener(this);

		// add diagrams tree listener to tree and tree model
		treeDiagrams.addTreeSelectionListener(diagramsTreeListener);
		treeDiagrams.addMouseListener(diagramsTreeListener);
		treeModel.addTreeModelListener(diagramsTreeListener);
		
		LOGGER.debug(
				"number of tree selection listeners for diagrams tree: " +
				treeDiagrams.getTreeSelectionListeners().length);

		treeDiagrams.setRootVisible(false);
		treeDiagrams.setModel(treeModel);
  }

	private void updateTreeTypes()
  {
		// shortcut
		JTree treeTypes = context.getTreeTypes();
		
		treeTypes.removeAll();
		
		Map<PackageElement, Set<TypeElement>> typesByPackage =
				new HashMap<PackageElement, Set<TypeElement>>();
		
		if (typeElementDictionary.getAll().length == 0)
		{
			LOGGER.debug("dictionary contains 0 type elements");
			JPanelVisualProcessor visualProcessor = context.getVisualProcessor();
			if (visualProcessor == null)
			{
				LOGGER.warn("absent visual processor");
			}
			else
			{
				visualProcessor.launchProcessor();
				LOGGER.debug(
						"dictionary contains " + typeElementDictionary.getAll().length +
						" type elements after launch of visual processor");
			}
		}
		
		for (TypeElement typeElement : typeElementDictionary.getAll())
		{
			PackageElement packageElement =
					ProcessingUtilities.getPackageOf(typeElement);
			
			// try to find types for package
			Set<TypeElement> typeElementsInPackage =
					typesByPackage.get(packageElement);
			
			if (typeElementsInPackage == null)
			{
				// create new type elements in package and put it into types by package
				typeElementsInPackage = new HashSet<TypeElement>();
				typesByPackage.put(packageElement, typeElementsInPackage);
			}
			
			typeElementsInPackage.add(typeElement);
		}

		// set cell renderer
		treeTypes.setCellRenderer(new TypesTreeCellRenderer());
		
		// no cell editor for this tree
//		treeTypes.setCellEditor(
//				new DiagramsTreeCellEditor(
//						treeTypes,
//						(DefaultTreeCellRenderer) treeTypes.getCellRenderer()));
		
		// make tree cells not editable
		treeTypes.setEditable(true);
		
		// show collapse / expand handles on root nodes
		treeTypes.setShowsRootHandles(true);

		// define root node
		DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();

		for (PackageElement packageElement : typesByPackage.keySet())
		{
			// populate tree
			updateTreePackage(
					rootNode, packageElement, typesByPackage.get(packageElement));
		}

		// build new tree model from root node
		TreeModel treeModel = new DefaultTreeModel(rootNode);
		
		// remove all tree selection listeners
		TreeSelectionListener[] treeSelectionListeners =
				treeTypes.getTreeSelectionListeners();
		
		for (TreeSelectionListener treeSelectionListener : treeSelectionListeners)
		{
			treeTypes.removeTreeSelectionListener(treeSelectionListener);
		}
		
		// do not remove all mouse listeners from tree, otherwise the mouse will
		// not work for the tree
		
		// create new diagrams tree listener
		typesTreeListener = new TypesTreeListener(this);

		// add types tree listener to tree and tree model
		treeTypes.addTreeSelectionListener(typesTreeListener);
		treeTypes.addMouseListener(typesTreeListener);
		
		treeTypes.setRootVisible(false);
		treeTypes.setModel(treeModel);
  }

	private void updateTreeDiagramGroup(
			DefaultMutableTreeNode parentNode,
      DiagramGroup diagramGroup)
  {
		// create node for diagramGroup and add it to parentNode
		DefaultMutableTreeNode diagramGroupNode =
				new DefaultMutableTreeNode(diagramGroup);
		parentNode.add(diagramGroupNode);
		
		// recurse into nested groups
		for (DiagramGroup nestedDiagramGroup : diagramGroup.getChildGroups())
		{
			updateTreeDiagramGroup(diagramGroupNode, nestedDiagramGroup);
		}

		// create nodes for diagrams in diagramGroup
		for (DesktopPane.JAXBModel diagramModel : diagramGroup.getDiagrams())
		{
			DefaultMutableTreeNode diagramNode =
					new DefaultMutableTreeNode(diagramModel);
			
			diagramGroupNode.add(diagramNode);
		}
  }

	private void updateTreePackage(
			DefaultMutableTreeNode parentNode,
      PackageElement packageElement,
      Set<TypeElement> typeElements)
  {
		// create node for package element and add it to parentNode
		DefaultMutableTreeNode packageElementNode =
				new DefaultMutableTreeNode(packageElement);
		parentNode.add(packageElementNode);
		
		// recurse into nested groups
		for (TypeElement typeElement : typeElements)
		{
			DefaultMutableTreeNode typeElementNode =
					new DefaultMutableTreeNode(typeElement);
			
			packageElementNode.add(typeElementNode);
		}
  }

	/**
	 * @param diagramModel
	 * @return <code>true</code> if the diagram represented by <code>diagramModel
	 *         </code> is not already in one of the tabbed panes
	 */
	public boolean displayOptionOpenDiagram(JAXBModel diagramModel)
  {
		if (context.getTabbedPaneDiagrams().indexOfTab(diagramModel.name) < 0)
		{
			return true;
		}
		
		return false;
  }

	/**
	 * @param typeElement
	 * @return <code>true</code> if the type element represented by <code>
	 *         typeElement</code> is not already on the selected desktop pane
	 */
	public boolean displayOptionAddTypeElement(TypeElement typeElement)
  {
		DesktopPane desktopPane = getSelectedDesktopPane();
		
		if (desktopPane == null)
		{
			return false;
		}

		Set<TypeShape<?>> typeShapes = desktopPane.getTypeShapes();
		
		if (typeShapes.size() == 0)
		{
			// selected desktop pane is empty
			return true;
		}
		
		for (TypeShape<?> typeShape : typeShapes)
		{
			if (typeShape.getTypeElement().getQualifiedName().toString().equals(
			                   typeElement.getQualifiedName().toString()))
			{
				return false;
			}
		}
		
	  return true;
  }

	public void openDiagram(DesktopPane.JAXBModel diagramModel)
  {
		DesktopPane desktopPane = new DesktopPane(diagramModel);

		JScrollPane scrollPane = new JScrollPane();
    
    scrollPane.getViewport().add(desktopPane);

		// xxx
		context.getTabbedPaneDiagrams().add(diagramModel.name, scrollPane);
//		context.getTabbedPaneDiagrams().add(diagramModel.name, desktopPane);
  }

	public void closeDiagram(DesktopPane.JAXBModel diagramModel)
  {
		JTabbedPane pane = context.getTabbedPaneDiagrams();
		pane.remove(pane.indexOfTab(diagramModel.name));
  }

	/**
	 * Deletes first found occurance of <code>diagramModel</code> from {@link
	 * #model} and calls {@link #updateTreeDiagrams()} if removal was successful.
	 *
	 * @param diagramModel
	 */
	public void deleteDiagram(DesktopPane.JAXBModel diagramModel)
  {
	  boolean removedDiagram = false;
	  
	  for (DiagramGroup diagramGroup : model.getDiagramGroups())
	  {
	  	for (DesktopPane.JAXBModel diagramModelInGroup : diagramGroup.getDiagrams())
	  	{
	  		if (diagramModel == diagramModelInGroup)
	  		{
	  			diagramGroup.getDiagrams().remove(diagramModel);
	  			removedDiagram = true;
	  			break;
	  		}
	  	}

  		if (removedDiagram)
  		{
  			break;
  		}
	  }

		if (removedDiagram)
		{
			updateTreeDiagrams();
		}
		else
		{
			LOGGER.error("failure deleting diagram " + diagramModel.name);
		}
  }

	public void newDiagramGroup()
  {
		// create default diagram group and add it to the model's diagram groups
		model.getDiagramGroups().add(DiagramGroup.createDefaultDiagramGroup());

		updateTreeDiagrams();
  }

	public void newDiagramSubGroup(DiagramGroup parentDiagramGroup)
  {
		// just create the new diagram group with it's parent set correctly through
		// the constructor
		// unlike in newDiagramGroup there's no further action necessary here
		new DiagramGroup(parentDiagramGroup);

		updateTreeDiagrams();
  }

	public void newDiagram(DiagramGroup containingDiagramGroup)
  {
		containingDiagramGroup.getDiagrams()
		                      .add(DesktopPane.createDefaultDesktopPane()
		                                      .getJAXBModel());
		
		updateTreeDiagrams();
  }

	/**
	 * @param diagramGroup
	 */
	public void deleteDiagramGroup(DiagramGroup diagramGroup)
  {
		if (diagramGroup.getParentGroup() == null)
		{
			if (model.getDiagramGroups().remove(diagramGroup))
			{
				updateTreeDiagrams();
			}
			else
			{
				LOGGER.error(
						"failure deleting top level diagram group " +
						diagramGroup.getName());
			}
		}
		else
		{
			if (DiagramGroup.releaseDiagramGroup(diagramGroup))
			{
				updateTreeDiagrams();
			}
			else
			{
				LOGGER.error(
						"failure deleting diagram group " + diagramGroup.getName());
			}
		}
  }

	public void rename(TreePath treePath)
  {
		context.getTreeDiagrams().startEditingAtPath(treePath);
  }

	public void addTypeElement(TypeElement typeElement)
  {
		DesktopPane desktopPane = getSelectedDesktopPane();
		
		if (desktopPane == null)
		{
			return;
		}
		
		TypeShape<TypeElement> typeShape = new TypeShape<TypeElement>(typeElement);

		desktopPane.add(typeShape);

		typeShape.setVisible(true);
  }
	
	private DesktopPane getSelectedDesktopPane()
	{
		Component selectedComponent =
				context.getTabbedPaneDiagrams().getSelectedComponent();
		
		if (selectedComponent == null)
		{
			// no component (desktop pane) selected in tabbed pane
			return null;
		}
		
		if (selectedComponent instanceof JScrollPane)
		{
			LOGGER.debug("JScrollPane component selected");
			JScrollPane jScrollPane = (JScrollPane) selectedComponent;

			selectedComponent = jScrollPane.getComponent(0);
			LOGGER.debug(
					"reassigned selected component to " +
					selectedComponent.getClass().getName() + " object");
		}
		
		if (selectedComponent instanceof JViewport)
		{
			LOGGER.debug("JViewport component selected");
			JViewport jViewport = (JViewport) selectedComponent;

			selectedComponent = jViewport.getComponent(0);
			LOGGER.debug(
					"reassigned selected component to " +
					selectedComponent.getClass().getName() + " object");
		}
		
		if (false == (selectedComponent instanceof DesktopPane))
		{
			LOGGER.debug(
					"unexpected type " + selectedComponent.getClass().getName() +
					" for selected component");
			return null;
		}
		
		return (DesktopPane) selectedComponent;
	}
}